package com.tus.handler;

import com.tus.model.UploadInfo;
import com.tus.storage.FileStorage;
import com.tus.store.MetadataStore;
import com.tus.util.UrlUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.*;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;


/**
 * 下载处理器 支持 HTTP Range 断点续传
 */
@Slf4j
public class DownloadHandler implements Handler {

    private final String downloadPathPrefix;
    /**
     * 元数据存储
     */
    private final MetadataStore metadataStore;
    private final FileStorage fileStorage;
    private static final Pattern RANGE_PATTERN = Pattern.compile("^bytes=(\\d+)-(\\d*)$");


    public DownloadHandler(MetadataStore metadataStore, FileStorage fileStorage, String downloadPathPrefix) {
        this.downloadPathPrefix = downloadPathPrefix;
        this.metadataStore = metadataStore;
        this.fileStorage = fileStorage;
    }

    private HttpHeaderValues.Range parseRange(String rangeHeader, long fileLength) {
        if (rangeHeader == null || !rangeHeader.startsWith("bytes=")) {
            return null; // 返回 null 表示请求完整文件
        }

        Matcher m = RANGE_PATTERN.matcher(rangeHeader);
        if (!m.matches()) {
            return null;
        }

        long start = Long.parseLong(m.group(1));
        String endStr = m.group(2);
        long end = endStr.isEmpty() ? fileLength - 1 : Long.parseLong(endStr);

        if (end >= fileLength) {
            end = fileLength - 1;
        }

        if (start < 0 || start > end) {
            return null;
        }

        return new HttpHeaderValues.Range(start, end);
    }

    private void sendFullFile(ChannelHandlerContext ctx, File file, long fileLength) throws Exception {
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        // 安全获取文件名
        String fileName = Paths.get(file.getPath()).getFileName().toString();
        // 对中文文件名进行编码
        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
        response.headers().set(CONTENT_TYPE, Files.probeContentType(file.toPath()) != null ? Files.probeContentType(file.toPath()) : "application/octet-stream").set(CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName).set(CONTENT_LENGTH, fileLength).set(ACCEPT_RANGES, "bytes");

        ctx.write(response);

        // 使用 DefaultFileRegion 实现零拷贝传输（推荐）
        DefaultFileRegion fileRegion = new DefaultFileRegion(raf.getChannel(), 0, fileLength);
        ctx.write(fileRegion);

        ChannelFuture lastFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);

        // 在传输完成后关闭文件
        lastFuture.addListener((ChannelFutureListener) future -> {
            try {
                raf.close();
            } catch (Exception e) {
                log.error("Failed to close RandomAccessFile", e);
            }
        });
    }

    private void sendPartialFile(ChannelHandlerContext ctx, File file, HttpHeaderValues.Range range, long fileLength) throws Exception {
        long start = range.getStart();
        long end = range.getEnd();
        long contentLength = end - start + 1;

        RandomAccessFile raf = new RandomAccessFile(file, "r");
        raf.seek(start);

        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, PARTIAL_CONTENT);
        response.headers().set(CONTENT_TYPE, Files.probeContentType(file.toPath()) != null ? Files.probeContentType(file.toPath()) : "application/octet-stream").set(CONTENT_LENGTH, contentLength).set(ACCEPT_RANGES, "bytes").set(CONTENT_RANGE, "bytes " + start + "-" + end + "/" + fileLength);

        ctx.write(response);
        ctx.write(new DefaultFileRegion(raf.getChannel(), start, contentLength));
        ChannelFuture lastFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);

        lastFuture.addListener((ChannelFutureListener) future -> {
            try {
                raf.close();
            } catch (Exception e) {
                log.error("close file error", e);
            }
        });
    }

    private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status, String message) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer(message, java.nio.charset.StandardCharsets.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public String getPathPrefix() {
        return downloadPathPrefix;
    }

    @Override
    public void execute(ChannelHandlerContext ctx, FullHttpRequest req) {
        String uri = req.uri();
        HttpMethod method = req.method();
        // 提取 uploadId
        String uploadId = UrlUtil.extractUploadId(uri, downloadPathPrefix);
        if (uploadId == null) {
            sendError(ctx, NOT_FOUND, "Invalid download path");
            return;
        }
        if (method == HttpMethod.GET) {
            //创建一个新的上传会话
            handleGet(ctx, req, uploadId);
        } else if (method == HttpMethod.HEAD) {
            handleHead(ctx, req, uploadId);
        }
    }

    private void handleGet(ChannelHandlerContext ctx, FullHttpRequest req, String uploadId) {
        try {
            File file = checkFile(ctx, uploadId);
            if (null == file) {
                sendError(ctx, NOT_FOUND, "File not found");
                return;
            }
            long fileLength = file.length();
            // 3. 解析 Range 请求头
            HttpHeaderValues.Range range = parseRange(req.headers().get(RANGE), fileLength);
            if (range == null) {
                // 没有 Range，返回完整文件
                sendFullFile(ctx, file, fileLength);
            } else {
                // 有 Range，返回部分内容
                sendPartialFile(ctx, file, range, fileLength);
            }
        } catch (Exception e) {
            sendError(ctx, INTERNAL_SERVER_ERROR, "Internal error: " + e.getMessage());
            log.error("download error", e);
        }
    }

    private File checkFile(ChannelHandlerContext ctx, String uploadId) {
        if (uploadId == null) {
            sendError(ctx, NOT_FOUND, "Invalid download path");
            return null;
        }

        // 2. 查询元数据
        UploadInfo info = metadataStore.findByUploadId(uploadId);
        if (info == null) {
            sendError(ctx, NOT_FOUND, "Upload not found");
            return null;
        }

        if (!info.isCompleted()) {
            sendError(ctx, NOT_FOUND, "File not completed yet");
            return null;
        }

        File file = fileStorage.getFile(info.getFilePath());
        if (!file.exists() || file.isDirectory()) {
            sendError(ctx, NOT_FOUND, "File not found");
            return null;
        }
        return file;
    }

    private void handleHead(ChannelHandlerContext ctx, FullHttpRequest req, String uploadId) {
        File file = checkFile(ctx, uploadId);
        if (null == file) {
            sendError(ctx, NOT_FOUND, "File not found");
            return;
        }
        // 安全获取文件名
        String fileName = Paths.get(file.getPath()).getFileName().toString();
        // 对中文文件名进行编码
        String encodedFileName;
        try {
            encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        try {
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
            // 必须的关键响应头
            response.headers().set(CONTENT_LENGTH, file.length());
            response.headers().set(CONTENT_TYPE, "application/octet-stream");
            response.headers().set(CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName);
            // 核心：告诉客户端支持字节范围请求
            response.headers().set(ACCEPT_RANGES, "bytes");
            // 发送响应
            ChannelFuture future = ctx.writeAndFlush(response);
            future.addListener(ChannelFutureListener.CLOSE);
        } catch (Exception e) {
            sendError(ctx, INTERNAL_SERVER_ERROR, "head method error");
            log.error("head method error", e);
        }

    }


    // ================== 辅助类 ==================

    public static class HttpHeaderValues {
        public static class Range {
            private final long start;
            private final long end;

            public Range(long start, long end) {
                this.start = start;
                this.end = end;
            }

            public long getStart() {
                return start;
            }

            public long getEnd() {
                return end;
            }
        }
    }

}
